use glam::{dvec3, DVec3};

use crate::{AABB, Hitable, Ray, Interval, HitRecord};

pub struct Translate {
    pub offset: DVec3,
    pub object: Box<dyn Hitable>,
    pub aabb: AABB,
}

impl Translate {
    pub fn new(offset: DVec3, object: impl Hitable + 'static) -> Self {
        let aabb = *object.bounding_box();
        Self {
            offset,
            object: Box::new(object),
            aabb: aabb + offset,
        }
    }
}

impl Hitable for Translate {
    fn hit(&self, ray: &Ray, interval: Interval) -> Option<HitRecord> {
        // transform the ray to object space
        let offseted_ray = Ray::new(
            ray.orig - self.offset,
            ray.dir,
        );

        // hit use offseted ray
        self.object
            .hit(&offseted_ray, interval)
            .and_then(|mut hit| {
                // transform hit position back to world space
                hit.p += self.offset;
                Some(hit)
            })
    }

    fn bounding_box(&self) -> &AABB {
        &self.aabb
    }
}

pub struct RotateY {
    pub sin_theta: f64,
    pub cos_theta: f64,
    pub object: Box<dyn Hitable>,
    pub aabb: AABB,
}

impl RotateY {
    pub fn new(angle: f64, object: Box<dyn Hitable>) -> Self {
        let theta = angle.to_radians();

        let sin_theta = theta.sin();
        let cos_theta = theta.cos();
        let aabb = object.bounding_box().clone();

        let mut min = dvec3(f64::INFINITY, f64::INFINITY, f64::INFINITY);
        let mut max = dvec3(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);

        for i in 0..2 {
            for j in 0..2 {
                for k in 0..2 {
                    let p = dvec3(
                        i as f64 * aabb.x.max + (1 - i) as f64 * aabb.x.min,
                        j as f64 * aabb.y.max + (1 - j) as f64 * aabb.y.min,
                        k as f64 * aabb.z.max + (1 - k) as f64 * aabb.z.min,
                    );
                    let p = dvec3(
                        cos_theta * p.x - sin_theta * p.z,
                        p.y,
                        sin_theta * p.x + cos_theta * p.z,
                    );

                    for c in 0..3 {
                        min[c] = min[c].min(p[c]);
                        max[c] = max[c].max(p[c]);
                    }
                }
            }
        }
        Self {
            sin_theta,
            cos_theta,
            object,
            aabb,
        }
    }
}

impl Hitable for RotateY {
    fn hit(&self, ray: &Ray, interval: Interval) -> Option<HitRecord> {
        let origin = dvec3(
            self.cos_theta * ray.orig.x - self.sin_theta * ray.orig.z,
            ray.orig.y,
            self.sin_theta * ray.orig.x + self.cos_theta * ray.orig.z,
        );

        let dir = dvec3(
            self.cos_theta * ray.dir.x - self.sin_theta * ray.dir.z,
            ray.dir.y,
            self.sin_theta * ray.dir.x + self.cos_theta * ray.dir.z,
        );

        self.object
            .hit(&Ray::new(origin, dir), interval)
            .and_then(|mut hit| {
                hit.p = dvec3(
                    self.cos_theta * hit.p.x + self.sin_theta * hit.p.z,
                    hit.p.y,
                    -self.sin_theta * hit.p.x + self.cos_theta * hit.p.z,
                );
                hit.n = dvec3(
                    self.cos_theta * hit.n.x + self.sin_theta * hit.n.z,
                    hit.n.y,
                    -self.sin_theta * hit.n.x + self.cos_theta * hit.n.z,
                );
                Some(hit)
            })
    }

    fn bounding_box(&self) -> &AABB {
        &self.aabb
    }
}